﻿#if SRP_URP
using Devil;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace GameToolkit
{
    public class CustomVolumeRenderer : ScriptableRendererFeature
    {
        [SerializeField]
        Object[] m_AssetsLib;

        List<System.Type> mComponentTypes;

        HashSet<ICacheableVolumeComponent> mCacheables;

        MinHeap<VolumePassData> mPassData;

        List<PassBuffer> mPassBuffer;
        BaseVolumeComponent[] mActiveVolume;
        int mActiveCount;
        List<ScriptableRenderPass> mActivePass;
        BaseVolumeComponent mCurrentComponent;

        public int ActiveVolumeCount => mActiveCount;
        public BaseVolumeComponent GetActiveVolumeComponent(int index)
        {
            return mActiveVolume[index];
        }

        public T GetAsset<T>(string assetName) where T : Object
        {
            var len = m_AssetsLib == null ? 0 : m_AssetsLib.Length;
            for (int i = 0; i < len; i++)
            {
                if (m_AssetsLib[i] is T asset && assetName.EqualsIgnoreCase(asset.name))
                    return asset;
            }
            return null;
        }

        public T GetAsset<T>(FilterDelegate<T> where) where T : Object
        {
            var len = m_AssetsLib == null ? 0 : m_AssetsLib.Length;
            for (int i = 0; i < len; i++)
            {
                if (m_AssetsLib[i] is T asset && where(asset))
                    return asset;
            }
            return null;
        }

        void GetComponentTypes()
        {
            var types = VolumeManager.instance.baseComponentTypeArray;
            if (types == null)
                return;
            if (mComponentTypes == null)
                mComponentTypes = new List<System.Type>();
            else
                mComponentTypes.Clear();
            var interfaceType = typeof(BaseVolumeComponent);
            for (int i = 0; i < types.Length; i++)
            {
                if (GlobalUtil.IsTypeInheritedFrom(types[i], interfaceType))
                {
                    mComponentTypes.Add(types[i]);
                }
            }
        }

        int ComparePassRenderQ(VolumePassData a, VolumePassData b)
        {
            if (a.pass.renderPassEvent == b.pass.renderPassEvent)
                return a.sortOrder < b.sortOrder ? -1 : (a.sortOrder > b.sortOrder ? 1 : 0);
            else
                return a.pass.renderPassEvent - b.pass.renderPassEvent;
        }

        public override void Create()
        {
            GetComponentTypes();
            if (mCacheables == null)
                mCacheables = new HashSet<ICacheableVolumeComponent>();
            if (mPassBuffer == null)
                mPassBuffer = new List<PassBuffer>(8);
            if (mPassData == null)
                mPassData = new MinHeap<VolumePassData>(16, ComparePassRenderQ);
            if (mActivePass == null)
                mActivePass = new List<ScriptableRenderPass>();
           
            if (!isActive)
            {
                for (int i = 0; i < mActiveCount; i++)
                {
                    var com = mActiveVolume[i];
                    mActiveVolume[i] = null;
                    if (com != null)
                    {
                        com.OnDeactivite(this);
                    }
                }
                mActiveCount = 0;
                for (int i = 0; i < mPassBuffer.Count; i++)
                {
                    mPassBuffer[i].Dispose();
                }
                foreach (var cacheable in mCacheables)
                {
                    cacheable.ReleaseCacheableAssets();
                }
                mCacheables.Clear();
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            for (int i = 0; i < mActiveCount; i++)
            {
                var com = mActiveVolume[i];
                mActiveVolume[i] = null;
                if (com != null)
                {
                    com.OnDeactivite(this);
                }
            }
            mActiveCount = 0;
            foreach (var cacheable in mCacheables)
            {
                cacheable.ReleaseCacheableAssets();
            }
            mCacheables.Clear();
        }

        int IndexOfActivitied(BaseVolumeComponent com)
        {
            for (int i = 0; i < mActiveCount; i++)
            {
                if (mActiveVolume[i] == com)
                    return i;
            }
            return -1;
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            var cam = renderingData.cameraData;
            if (!cam.postProcessEnabled)
                return;
#if UNITY_EDITOR
            if (cam.isSceneViewCamera)
            {
                var scene = UnityEditor.SceneView.currentDrawingSceneView;
                if (scene != null && !scene.sceneViewState.imageEffectsEnabled)
                    return;
            }
#endif
            VolumeStack stack = VolumeManager.instance.stack;
            if (stack == null)
                return;
           
            int activeCount = 0;
            var dirtyLst = CachedList<BaseVolumeComponent>.GetList();
            var dirty = false;
            for (int i = 0; i < mComponentTypes.Count; i++)
            {
                var com = stack.GetComponent(mComponentTypes[i]) as BaseVolumeComponent;
                if (com != null && com.IsActive())
                {
                    mCurrentComponent = com;
                    var index = IndexOfActivitied(com);
                    if (index == -1)
                    {
                        NativeArrayUtil.EnsureNativeSize(ref mActiveVolume, mActiveCount, mActiveCount + 1);
                        if (mActiveCount > activeCount)
                            mActiveVolume[mActiveCount] = mActiveVolume[activeCount];
                        mActiveVolume[activeCount] = com;
                        activeCount++;
                        mActiveCount++;
                        dirtyLst.Add(com);
                        dirty = true;
                    }
                    else if (index > activeCount)
                    {
                        mActiveVolume[index] = mActiveVolume[activeCount];
                        mActiveVolume[activeCount] = com;
                        activeCount++;
                        dirty |= com.IsRenderPassDirty();
                    }
                    else if (index == activeCount)
                    {
                        activeCount++;
                        dirty |= com.IsRenderPassDirty();
                    }
                }
            }
            var newCount = dirtyLst.Count;
            for (int i = activeCount; i < mActiveCount; i++)
            {
                var com = mActiveVolume[i];
                if (com != null)
                {
                    dirty = true;
                    dirtyLst.Add(com);
                }
                mActiveVolume[i] = null;
            }
            mActiveCount = activeCount;
            for (int i = newCount; i < dirtyLst.Count; i++)
            {
                dirtyLst[i].OnDeactivite(this);
            }
            for (int i = 0; i < newCount; i++)
            {
                dirtyLst[i].OnActivite(this);
                if (dirtyLst[i] is ICacheableVolumeComponent cacheable)
                    mCacheables.Add(cacheable);
            }
            if (dirty)
            {
                for (int i = 0; i < mPassBuffer.Count; i++)
                {
                    mPassBuffer[i].Reset();
                }
                for (int i = 0; i < mActiveCount; i++)
                {
                    mCurrentComponent = mActiveVolume[i];
                    mCurrentComponent.OnSetupVolumePasses(this);
                }
                mActivePass.Clear();
                while (mPassData.Count > 0)
                {
                    mActivePass.Add(mPassData.RemoveTop().pass);
                }
            }
            CachedList<BaseVolumeComponent>.Release(dirtyLst);
            for (int i = 0; i < mActivePass.Count; i++)
            {
                renderer.EnqueuePass(mActivePass[i]);
            }
        }

        TPass GetPassFromBuffer<TPass>()
            where TPass : ScriptableRenderPass, new()
        {
            for (int i = 0; i < mPassBuffer.Count; i++)
            {
                if (mPassBuffer[i] is PassBuffer<TPass> buf)
                    return buf.GetPass();
            }
            var buffer = new PassBuffer<TPass>(4);
            mPassBuffer.Add(buffer);
            return buffer.GetPass();
        }

        public TPass Enqueue<TPass>(BaseVolumeComponent com, RenderPassEvent renderEvent, int sortOrder)
            where TPass : ScriptableRenderPass, new()
        {
            if (mCurrentComponent == null || !mCurrentComponent.Equals(com))
                return null;
            var pass = GetPassFromBuffer<TPass>();
            pass.renderPassEvent = renderEvent;
            VolumePassData passData;
            passData.sortOrder = sortOrder;
            passData.pass = pass;
            mPassData.Add(passData);
            return pass;
        }

        struct VolumePassData
        {
            public int sortOrder;
            public ScriptableRenderPass pass;
        }

        abstract class PassBuffer
        {
            public abstract void Reset();
            public abstract void Dispose();
        }

        class PassBuffer<TPass> : PassBuffer
            where TPass : ScriptableRenderPass, new()
        {
            int mIndex;
            bool mDisposable;
            List<TPass> mPasses;
            public PassBuffer(int size)
            {
                mPasses = new List<TPass>(size > 4 ? size : 4);
                var pass = new TPass();
                mPasses.Add(pass);
                mDisposable = pass is System.IDisposable;
            }

            public override void Reset()
            {
                mIndex = 0;
            }

            public override void Dispose()
            {
                if (mDisposable)
                {
                    for (int i = 0; i < mPasses.Count; i++)
                    {
                        ((System.IDisposable)mPasses[i]).Dispose();
                    }
                    mPasses.Clear();
                }
            }

            public TPass GetPass()
            {
                if (mIndex < mPasses.Count)
                    return mPasses[mIndex++];
                var pass = new TPass();
                mPasses.Add(pass);
                mIndex = mPasses.Count;
                return pass;
            }
        }

    }
}
#endif